package com.atguigu.daijia.map.service.impl;

import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.constant.SystemConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.Result;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.common.util.LocationUtil;
import com.atguigu.daijia.driver.client.DriverInfoFeignClient;
import com.atguigu.daijia.map.repository.OrderServiceLocationRepository;
import com.atguigu.daijia.map.service.LocationService;
import com.atguigu.daijia.model.entity.driver.DriverSet;
import com.atguigu.daijia.model.entity.map.OrderServiceLocation;
import com.atguigu.daijia.model.form.map.OrderServiceLocationForm;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.form.map.UpdateDriverLocationForm;
import com.atguigu.daijia.model.form.map.UpdateOrderLocationForm;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.map.OrderLocationVo;
import com.atguigu.daijia.model.vo.map.OrderServiceLastLocationVo;
import com.atguigu.daijia.order.client.OrderInfoFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {


    /**
     * 注入MongoDB存储订单代驾服务位置
     */
    @Autowired
    private OrderServiceLocationRepository orderServiceLocationRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    /**
     * 更新司机经纬度位置
     * 这个是将经纬度位置放到GEO中
     * @param updateDriverLocationForm
     * @return
     */
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        //把司机位置信息添加redis里面geo
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
                updateDriverLocationForm.getLatitude().doubleValue());

        //添加到redis里面
        String driverId = String.valueOf(updateDriverLocationForm.getDriverId());

        // 司机位置的键
        String key = RedisConstant.DRIVER_GEO_LOCATION;

        // 添加司机的位置信息到 Redis
        redisTemplate.opsForGeo().add(key, point, driverId);

        return true;
    }

    /**
     * 删除司机经纬度位置
     *
     * @param driverId
     * @return
     */
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        String key = RedisConstant.DRIVER_GEO_LOCATION;
        redisTemplate.opsForGeo().remove(key, driverId.toString());
        return true;
    }

    //搜索附近满足条件的司机
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        if (searchNearByDriverForm.getLatitude() == null) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR.getCode(), "经度不能为空");
        }
        if (searchNearByDriverForm.getLatitude() == null) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR.getCode(), "伟度不能为空");
        }
        if (searchNearByDriverForm.getMileageDistance() == null) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR.getCode(), "里程不能为空");
        }
        //搜索经纬度位置5公里以内的司机
        if (searchNearByDriverForm.getSearchRadius() == null ||
                searchNearByDriverForm.getSearchRadius().isInfinite() ||
                searchNearByDriverForm.getSearchRadius().equals(0.0)) {
            searchNearByDriverForm.setSearchRadius(SystemConstant.NEARBY_DRIVER_RADIUS);
        }
        //1 操作redis里面geo
        //创建point，经纬度位置
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
                searchNearByDriverForm.getLatitude().doubleValue());

        //定义距离
        Distance distance = new Distance(searchNearByDriverForm.getSearchRadius(),
                RedisGeoCommands.DistanceUnit.KILOMETERS);

        //创建circle对象，point  distance  以某个点为中心，向外扩大五公里范围
        Circle circle = new Circle(point, distance);

        //定义GEO参数，设置返回结果包含内容
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                        .includeDistance()  //包含距离
                        .includeCoordinates() //包含坐标
                        .sortAscending(); //升序

        //位于指定地理圆形区域内的成员
        GeoResults<RedisGeoCommands.GeoLocation<String>> result =
                redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

        //2 查询redis最终返回list集合
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

        //3 对查询list集合进行处理
        // 遍历list集合，得到每个司机信息
        // 根据每个司机个性化设置信息判断
        List<NearByDriverVo> list = new ArrayList<>();
        if (!CollectionUtils.isEmpty(content)) {
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
            //遍历集合
            while (iterator.hasNext()) {
                GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();
                //获取司机id
                Long driverId = Long.parseLong(item.getContent().getName());
                //远程调用，根据司机id个性化设置信息
                Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
                DriverSet driverSet = driverSetResult.getData();
                //判断订单里程order_distance 订单需要多远里程
                BigDecimal orderDistance = driverSet.getOrderDistance();
                //orderDistance==0，司机没有限制的
                //如果不等于0 ，比如30，接单30公里代驾订单。
                //接单距离 - 当前单子距离  < 0,不复合条件
                // 30          35
                if (orderDistance.doubleValue() != 0
                        &&
                        //BigDecimal相减  司机接单距离  - 当前单子距离
                        orderDistance.subtract(searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
                    continue;
                }

                //判断接单里程 accept_distance
                //当前接单距离  单子离自己多元
                BigDecimal currentDistance =
                        new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);

                //接单里程设置
                BigDecimal acceptDistance = driverSet.getAcceptDistance();
                if (acceptDistance.doubleValue() != 0
                        && acceptDistance.subtract(currentDistance).doubleValue() < 0) {
                    continue;
                }
                //封装复合条件数据
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);
            }

        }
        return list;
    }

    /**
     * 司机赶往代驾起始点：更新订单地址到缓存
     * @param updateOrderLocationForm 这个是将
     * @return
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {

        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());

        //将经纬度位置信息，设置到Redis中
        String key = RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId();
        redisTemplate.opsForValue().set(key,orderLocationVo);
        return true;
    }

    /**
     * 根据订单id，获取订单司机位置信息
     * @param orderId
     * @return
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        String key = RedisConstant.UPDATE_ORDER_LOCATION + orderId;
        OrderLocationVo orderLocationVo = (OrderLocationVo)redisTemplate.opsForValue().get(key);
        if (orderLocationVo == null) {
           throw new GuiguException(ResultCodeEnum.DATA_ERROR.getCode(),"司机位置信息未更新！");
        }
        return orderLocationVo;
    }


    /**
     * 保存代驾服务订单位置  保存到mongoDB
     * @param orderLocationServiceFormList
     * @return
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        List<OrderServiceLocation> list = new ArrayList<>();
        orderLocationServiceFormList.forEach(item -> {
            //批量构建订单位置信息
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(item, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());
            list.add(orderServiceLocation);
        });
        //批量保存
        orderServiceLocationRepository.saveAll(list);
        return true;
    }

    /**
     * 代驾服务：
     * 从mongoDB获取订单服务最后一个位置信息
     * 根据订单id,和创建时间倒序查询
     * @param orderId
     * @return
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        query.limit(1);
        OrderServiceLocation orderServiceLocation
                = mongoTemplate.findOne(query, OrderServiceLocation.class);
        //封装返回对象
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);
        return orderServiceLastLocationVo;
    }

    /**
     * 根据订单id，获取MogoDB存储的经纬度位置信息，分别计算出订单的实际里程
     * 实际距离就是取mongoDB中上传的位置集合，然后循环计算两个坐标点的距离，然后求和
     * @param orderId
     * @return
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //获取订单服务位置信息
        OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
        //设置查询条件
        orderServiceLocation.setOrderId(orderId);
        Example<OrderServiceLocation> example = Example.of(orderServiceLocation);
        //设置升序排序
        Sort sort = Sort.by(Sort.Direction.ASC, "createTime");
        List<OrderServiceLocation> orderServiceLocationList
                = orderServiceLocationRepository.findAll(example,sort);

        //第二种实现方式，按照MongoRepository规则，把查询方法创建出来就行了
        //总体规则：
        //1.查询方法名称 以 get | find |read 开头
        //2.后面加上查询字段名称 满足驼峰式命名 如：OrderId
        //3.字段查询条件添加关键字 比如：like OrderBy Asc
        //具体编写
        List<OrderServiceLocation> list =
                orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);

        // 计算订单实际里程
        double realDistance = 0;
        if (orderServiceLocationList.size() > 0) {
            for (int i = 0, size=orderServiceLocationList.size()-1; i < size; i++) {
                // 计算两点之间的距离
                OrderServiceLocation location1 = orderServiceLocationList.get(i);
                OrderServiceLocation location2 = orderServiceLocationList.get(i+1);
                double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(), location1.getLongitude().doubleValue(), location2.getLatitude().doubleValue(), location2.getLongitude().doubleValue());
                realDistance += distance;
            }
        }
        //测试过程中，没有真正代驾，实际代驾GPS位置没有变化，模拟：实际代驾里程 = 预期里程 + 5
        if(realDistance == 0) {
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal("5"));
        }
        return new BigDecimal(realDistance);
    }
}
